package com.filepublic.demo.server.service.impl;

import com.filepublic.demo.common.constant.ExceptionConstant;
import com.filepublic.demo.common.constant.FileConstant;
import com.filepublic.demo.common.constant.RedisKeyConstant;
import com.filepublic.demo.common.exception.BaseLogicException;
import com.filepublic.demo.common.property.FileProperty;
import com.filepublic.demo.common.property.ImageProperty;
import com.filepublic.demo.common.util.FileUtil;
import com.filepublic.demo.pojo.dto.FileSureDto;
import com.filepublic.demo.pojo.entity.FileEntity;
import com.filepublic.demo.pojo.vo.FileDownloadVo;
import com.filepublic.demo.pojo.vo.FileSureVo;
import com.filepublic.demo.pojo.vo.ImageVo;
import com.filepublic.demo.server.mapper.FileBaseMapper;
import com.filepublic.demo.server.service.AsyncService;
import com.filepublic.demo.server.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class FileServiceImpl implements FileService {

    @Resource
    RedisTemplate redisTemplate;

    @Resource
    FileProperty fileProperty;

    @Resource
    ImageProperty imageProperty;

    @Resource
    FileBaseMapper fileBaseMapper;

    @Resource
    AsyncService asyncService;

    /**
     * 文件上传
     * @param file
     * @param fileIndex
     * @param fileId
     */
    @Override
    public void bigFileUpload(MultipartFile file, Integer fileIndex, String fileId) throws IOException {
        //将fileId作为set的key，fileIndex作为value
        SetOperations setOperations = redisTemplate.opsForSet();
        setOperations.add(RedisKeyConstant.BIG_FILE_CHUNK_KEY+fileId,fileIndex);
        log.info("执行上传：{}",fileId);

        //创建新的文件目录
        String newFilePath = fileProperty.getFileOriginPath()+ File.separator + fileId + File.separator;
        Files.createDirectories(Paths.get(newFilePath));

        //保存的文件名(分片无后缀名)
        String fileName = fileId + fileIndex;

        //异步保存对应文件到新的文件目录下
        asyncService.saveFile(file,new File(newFilePath+fileName));
    }

    /**
     * 文件确认
     * @param fileSureDto
     * 文件确认，在每次上传前都应该使用一下，如果没有上传过，直接从0上传，如果上传过，则返回未上传完的下标，如果上传完了，则保存数据库
     */
    @Override
    public FileSureVo bigFileSure(FileSureDto fileSureDto) {
        //返回对象
        FileSureVo fileSureVo;

        //查询是否上传过
        if(!isUploaded(fileSureDto.getFileId())){
            //将文件信息上传到redis中
            uploadFileInfo(fileSureDto);

            //返回信息，表示没有上传过
            fileSureVo = FileSureVo.builder()
                    .uploaded(false)
                    .build();
            return fileSureVo;
        }

        //查找未上传的文件块
        List<Integer> lackFileChunks = findLackFileChunks(fileSureDto);

        //不为空时，则证明文件未上传完成
        if(!lackFileChunks.isEmpty()){
            fileSureVo = FileSureVo.builder()
                    .uploaded(true)
                    .uploadOver(false)
                    .lackFileChunks(lackFileChunks)
                    .build();
            return fileSureVo;
        }

        //文件上传完成，将文件保存到数据库中
        FileEntity fileEntity = FileEntity.builder()
                .fileId(fileSureDto.getFileId())
                .fileName(fileSureDto.getFileName())
                .fileSize(fileSureDto.getFileSize())
                .fileChunksNum(fileSureDto.getFileChunkNum())
                .build();
        fileBaseMapper.insertFile(fileEntity);

        fileSureVo = FileSureVo.builder()
                .uploaded(true)
                .uploadOver(true)
                .fileId(fileSureDto.getFileId())
                .build();
        return fileSureVo;
    }

    /**
     * 文件下载
     * @param fileId
     * @param fileIndex
     */
    @Override
    public FileDownloadVo bigFileDownload(String fileId, Integer fileIndex) throws IOException {
        String filePath = fileProperty.getFileOriginPath()+ File.separator + fileId + File.separator;
        //保存的文件名(分片无后缀名)
        String fileName = fileId + fileIndex;

        //读取对应下标的文件
        byte[] fileBytes = FileUtil.readFileBytes(new File(filePath + fileName));

        FileDownloadVo fileDownloadVo = FileDownloadVo.builder()
                .fileBytes(fileBytes)
                .fileIndex(fileIndex)
                .fileId(fileId)
                .build();
        return fileDownloadVo;
    }

    /**
     * 图片上传
     * @param file
     * @return
     */
    @Override
    public ImageVo imageUpload(MultipartFile file) throws IOException {
        //获取文件后缀名
        String originalFilename = file.getOriginalFilename();
        int lastIndexOf = originalFilename.lastIndexOf(".");
        String suffix = originalFilename.substring(lastIndexOf);

        String fileName = UUID.randomUUID().toString();

        //生成新的文件名
        String newFileName = fileName+suffix;

        //获取存储地址
        String imageOriginPath = imageProperty.getImageOriginPath();

        //写入文件
        file.transferTo(new File(imageOriginPath+newFileName));

        ImageVo imageVo = ImageVo.builder()
                .url(FileConstant.LOCAL_HOST + FileConstant.IMAGE_ROOT + newFileName)
                .build();

        return imageVo;
    }

    //查询是否上传过
    public boolean isUploaded(String fileId){

        //string类型
        ValueOperations valueOperations = redisTemplate.opsForValue();

        //查询是否上传过
        FileSureDto fileInfo = (FileSureDto)valueOperations.get(RedisKeyConstant.BIG_FILE_INFO_KEY + fileId);

        if(fileInfo == null){
            return false;
        }
        return true;
    }

    //上传到redis的string中
    public void uploadFileInfo(FileSureDto fileSureDto){
        //string类型
        ValueOperations valueOperations = redisTemplate.opsForValue();

        //记录七天
        valueOperations.set(RedisKeyConstant.BIG_FILE_INFO_KEY + fileSureDto.getFileId(),fileSureDto,7, TimeUnit.DAYS);
    }

    //查找没上传的文件块
    public List<Integer> findLackFileChunks(FileSureDto fileSureDto){
        //set类型
        SetOperations setOperations = redisTemplate.opsForSet();

        //查询哪些片段没有上传
        Set<Integer> fileChunks = setOperations.members(RedisKeyConstant.BIG_FILE_CHUNK_KEY + fileSureDto.getFileId());
        log.info("set:{}",fileChunks);

        List<Integer> lackFileChunks = new ArrayList();

        for(int i = 0;i<fileSureDto.getFileChunkNum();i++){
            if(!fileChunks.contains(i)){
                lackFileChunks.add(i);
            }
        }

        return lackFileChunks;
    }
}



















